<script setup>
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { cloneDeep } from 'lodash'
import { Message, Modal, Notification } from '@arco-design/web-vue'
import { getModelAccuracy, getModelList, getParamSpinner, getProcessParam, getTelemetry } from '@/api/fault/fault'
import { checkAlgorithm, getAlgorithmByType } from '@/api/algorithm/algorithm'
import { beginTrain, deleteModelTaskById } from '@/api/trend/trend'
import dayjs from 'dayjs'
import { MODEL_TRAIN_STATUS } from '@/utils/enumTypes'
import ChartLine from '@/components/chart/ChartLine.vue'
import { useWebsocketStore } from '@/store/modules/websocket'
import { useUserStore } from '@/store/modules/user'

const userStore = useUserStore()

const props = defineProps({
  satelliteInfo: Object
})
const ws = useWebsocketStore()

const formLoading = ref(false)
const TRAIN_FORM = {
  satelliteId: '',
  satelliteCode: '',
  paramCode: '',
  paramCodeName: '',
  startTime: '2022-01-01 00:00:00',
  endTime: '2022-01-02 00:00:00',
  algorithmName: '',
  containFault: 1,
  faultStartTime: '',
  faultEndTime: '',
  modelType: 1,
  isOldModel: false,
  oldModelName: '',
  modelName: '',
  calculateUpdateModelParam: {
    filters: '',
    activation: '',
    units: '',
    lstmActivation: '',
    denseActivation: '',
    lossMetric: '',
    optimizer: '',
    epochs: '',
    batchSize: ''
  },
  algorithmParamList: []
}
const trainForm = ref(cloneDeep(TRAIN_FORM))
const queryForm = ref({
  startTime: '',
  endTime: '',
  satelliteCode: '',
  paramCodes: []
})

const scroll = {
  x: '1200px',
  y: '100%'
}

const chartLoading = ref(false)

const dataSource = ref({
  xAxisType: 'time',
  xAxis: [],
  yAxis: [
    {
      name: '原始曲线',
      color: '#0ab7b9',
      data: []
    },
    {
      name: '模型准确度测试数据',
      color: 'orange',
      data: []
    }
  ]
})

const columns = [
  {
    title: '卫星代号',
    dataIndex: 'satelliteCode'
  },
  {
    title: '参数名称',
    dataIndex: 'paramCodeName',
    tooltip: { position: 'top' },
    ellipsis: true,
    width: 150
  },
  {
    title: '算法名称',
    dataIndex: 'algorithmName'
  },
  {
    title: '模型名称',
    dataIndex: 'modelName'
  },
  {
    title: '开始时间',
    dataIndex: 'startTime',
    render: ({ record }) => {
      return dayjs(record.startTime).format('YYYY-MM-DD HH:mm:ss')
    },
    width: 175
  },
  {
    title: '结束时间',
    dataIndex: 'endTime',
    render: ({ record }) => {
      return dayjs(record.startTime).format('YYYY-MM-DD HH:mm:ss')
    },
    width: 175
  },
  {
    title: '偏差值',
    dataIndex: 'offset'
  },
  {
    title: '状态',
    dataIndex: 'status',
    render: ({ record }) => {
      return MODEL_TRAIN_STATUS[record.status]
    }
  },
  {
    title: '操作',
    slotName: 'action',
    width: 160,
    fixed: 'right',
    align: 'center'
  }
]
const tableLoading = ref(false)
const modelPageData = ref([])
const pagination = ref({
  pageNum: 1,
  pageSize: 10,
  total: 0
})

const loadList = async () => {
  if (!props.satelliteInfo.satelliteId) return
  try {
    modelPageData.value = []
    tableLoading.value = true
    // 非训练完成的模型都可以认为新模型
    const { code, data } = await getModelList({
      isOldModel: false,
      modelType: 1,
      page: pagination.value.pageNum,
      satelliteCode: props.satelliteInfo.code,
      pageSize: pagination.value.pageSize
    })
    if (code === 200) {
      modelPageData.value = data.list
      pagination.value.total = Number(data.total)
    }
  } catch (e) {
    console.log(e)
  } finally {
    tableLoading.value = false
  }
}
const handlePageChange = (page) => {
  pagination.value.pageNum = page
  loadList()
}

const handlePageSizeChange = (size) => {
  pagination.value.pageSize = size
  loadList()
}

const handleDetail = async (record) => {
  // 处理上边echarts
  try {
    chartLoading.value = true
    resetDataSource()
    queryForm.value.satelliteCode = record.satelliteCode
    queryForm.value.paramCodes = [record.paramCode]
    queryForm.value.startTime = record.startTime
    queryForm.value.endTime = record.endTime

    const { code, data } = await getTelemetry(queryForm.value)
    if (code === 200 && data != null) {
      const paramCode = queryForm.value.paramCodes[0]
      const list = data[paramCode]
      if (list.length !== 0) {
        list.forEach((x) => {
          dataSource.value.xAxis.push(x.time)
          dataSource.value.yAxis[0].data.push([x.time, x.value])
        })
      }
    }
  } catch (e) {
    console.log(e)
  } finally {
    chartLoading.value = false
  }

  try {
    trainForm.value.satelliteCode = record.satelliteCode
    formLoading.value = true
    // 获取模型详情信息
    const { code, data } = await getModelAccuracy({ id: record.id })
    if (code === 200) {
      // 获取旧模型列表
      await switchChange(true)
      // 给表单赋值展示详情
      trainForm.value.isOldModel = true
      trainForm.value.oldModelName = data.calculateModelTrain.modelName
      trainForm.value.paramCode = data.calculateModelTrain.paramCode
      trainForm.value.startTime = data.calculateModelTrain.startTime
      trainForm.value.endTime = data.calculateModelTrain.endTime
      trainForm.value.algorithmName = data.calculateModelTrain.algorithmName
      trainForm.value.algorithmParamList = data.calculateModelTrain.algorithmParamList
      // todo
      data.testModel.forEach((x) => {
        dataSource.value.yAxis[1].data.push([x.time, x.value])
      })
    }
  } catch (e) {
    console.log(e)
  } finally {
    formLoading.value = false
  }
}
const handleDelete = (record) => {
  Modal.confirm({
    title: '提示',
    content: `确认要删除当前模型吗?`,
    okText: '确认',
    cancelText: '取消',
    onOk: async () => {
      try {
        const { code, data, message } = await deleteModelTaskById(record.id)
        if (code === 200) {
          Message.success(data || '删除成功')
        } else {
          Message.error(message || '删除失败')
        }
      } catch (e) {
        console.log(e)
      } finally {
        await loadList()
      }
    }
  })
}

const startTrain = async (from) => {
  if (!trainFormRef.value) return false
  const res = await trainFormRef.value.validate()
  if (res !== undefined) return false
  try {
    const { code, message } = await beginTrain(from.values)
    if (code === 200) {
      Message.success('请等待训练完成或稍后在模型列表查看')
    } else {
      Message.error(message)
    }
  } catch (e) {
    if (e.code === 'ECONNABORTED') {
      const selectLabel = oldModelOptions.value.find((item) => item.id === from.values.oldModelName)?.modelName
      Notification.info({
        title: '提示',
        closable: true,
        content: `趋势预测模型${
          from.isOldModel ? selectLabel : from.modelName
        }训练耗时较长，请稍后刷新模型列表查看训练状态。`
      })
    }
  } finally {
    await loadList()
  }
}

function startTimeValidator(value, callback) {
  if (!value) return callback('请选择开始时间')
  if (trainForm.value.endTime && value > trainForm.value.endTime) {
    callback('开始时间不能大于结束时间')
  } else {
    callback()
  }
}

function endTimeValidator(value, callback) {
  if (!value) return callback('请选择结束时间')
  if (trainForm.value.startTime && value < trainForm.value.startTime) {
    callback('结束时间不能小于开始时间')
  } else {
    callback()
  }
}

const trainFormRules = {
  paramCode: [{ required: true, message: '请选择参数', trigger: ['change', 'input'] }],
  modelName: [{ required: true, message: '请填写模型名称', trigger: ['change', 'input'] }],
  oldModelName: [
    {
      required: true,
      message: '请选择旧模型',
      trigger: ['change', 'input']
    }
  ],
  startTime: [
    { required: true, message: '请选择开始时间', trigger: ['change', 'input'] },
    { validator: startTimeValidator }
  ],
  endTime: [
    { required: true, message: '请选择结束时间', trigger: ['change', 'input'] },
    { validator: endTimeValidator }
  ],
  algorithmName: [{ required: true, message: '请选择算法', trigger: ['change'] }]
}

const paramOptions = ref([])
const paramSelectLoading = ref(false)
const computedSearchSelectPlaceholder = computed(() => {
  if (!trainForm.value.satelliteId) {
    return '请先选择左侧卫星'
  } else {
    return '请选择参数'
  }
})
const handleSearch = async (queryStr) => {
  if (!trainForm.value.satelliteId) return Message.error('请先选择左侧卫星!')
  try {
    paramSelectLoading.value = true
    const { code, data } = await getParamSpinner(trainForm.value.satelliteId, queryStr)
    if (code === 200) {
      paramOptions.value = data
    }
  } catch (e) {
    console.log(e)
  } finally {
    paramSelectLoading.value = false
  }
}

const algorithmOptions = ref([])

const processParam = async (paramCode) => {
  try {
    const { code, data } = await getProcessParam({
      satelliteCode: trainForm.value.satelliteCode,
      paramCode
    })
    if (code === 200) {
      Notification.info({
        title: '提示',
        closable: true,
        content: data
      })
    }
  } catch (e) {
    console.log(e)
  }
}

const modelSelectLoading = ref(false)
const paramCodeChange = (paramCode) => {
  // 参数名赋值
  trainForm.value.paramCodeName = paramOptions.value.find((item) => item.telemetryCode === paramCode).paramName
  // 获取是否进行过数据预处理
  processParam(paramCode)
}

const oldModelOptions = ref([])

const switchChange = async (flag) => {
  if (!trainForm.value.satelliteCode) {
    Message.error('请先选择卫星')
    trainForm.value.isOldModel = !flag
    return
  }
  resetPartTrainForm(flag)
  if (flag) {
    try {
      modelSelectLoading.value = true
      const { code, data } = await getModelList({
        isOldModel: true,
        modelType: 1,
        page: pagination.value.pageNum,
        satelliteCode: props.satelliteInfo.code,
        pageSize: pagination.value.pageSize
      })
      if (code === 200) {
        oldModelOptions.value = data.list
      }
    } catch (e) {
      console.log(e)
    } finally {
      modelSelectLoading.value = false
    }
  }
}
const algorithmSelectLoading = ref(false)
// 选择算法
const algorithmSpinner = async () => {
  try {
    algorithmSelectLoading.value = true
    const { code, data } = await getAlgorithmByType(5)
    if (code === 200) {
      algorithmOptions.value = data
    }
  } catch (e) {
    console.log(e)
  } finally {
    algorithmSelectLoading.value = false
  }
}
const trainFormRef = ref(null)

const resetDataSource = () => {
  dataSource.value.xAxis = []
  dataSource.value.yAxis[0].data = []
  dataSource.value.yAxis[1].data = []
}
const resetTable = () => {
  modelPageData.value = []
  pagination.value.pageNum = 1
  pagination.value.total = 0
}
const resetTabs = () => {
  if (trainFormRef.value) {
    trainFormRef.value.clearValidate()
    trainFormRef.value.resetFields()
    trainForm.value = cloneDeep(TRAIN_FORM)
    resetTable()
    resetDataSource()
  }
}
watch(
  () => props.satelliteInfo,
  (newVal) => {
    resetTabs()
    if (newVal) {
      trainForm.value.satelliteId = newVal.satelliteId
      trainForm.value.satelliteCode = newVal.code
      trainForm.value.satelliteName = newVal.name
      handleSearch('')
      loadList()
      algorithmSpinner()
    } else {
      trainForm.value.satelliteId = trainForm.value.satelliteCode = trainForm.value.satelliteName = ''
      paramOptions.value = []
    }
  },
  { deep: true }
)
const resetPartTrainForm = (switchFlag = false) => {
  trainForm.value = cloneDeep(TRAIN_FORM)
  trainForm.value.isOldModel = switchFlag
  trainForm.value.satelliteId = props.satelliteInfo.satelliteId
  trainForm.value.satelliteCode = props.satelliteInfo.code
  trainForm.value.satelliteName = props.satelliteInfo.name
}
let originParamList = []
const algorithmSelectChange = async (name) => {
  if (name) {
    const id = algorithmOptions.value.find((item) => item.algorithmName === name).id
    try {
      if (trainForm.value.algorithmName === 'cnn') {
        const { code, data, message } = await checkAlgorithm(id)
        if (code === 200) {
          originParamList = cloneDeep(data.algorithmParams)
          trainForm.value.algorithmParamList = data.algorithmParams
        } else {
          Message.error(data || message)
        }
      }
    } catch (e) {
      console.log(e)
    }
  }
}

const editAlgorithmParasModelVisible = ref(false)
const editAlgorithmParams = () => {
  if (trainForm.value.algorithmName !== 'cnn') return Message.error('仅支持修改cnn参数')
  editAlgorithmParasModelVisible.value = true
}

const handleEditReset = () => {
  trainForm.value.algorithmParamList = cloneDeep(originParamList)
}
const handleBeforeCancel = () => {
  Modal.confirm({
    title: '提示',
    content: `是否保存当前参数？`,
    okText: '确定',
    cancelText: '取消',
    onOk: () => {
      editAlgorithmParasModelVisible.value = false
    },
    onCancel: () => {
      handleEditReset()
      editAlgorithmParasModelVisible.value = false
      Message.info('参数已重置')
    }
  })
}

const topic = '/topic/calculateModelTrial/' + userStore.userId

const sub = () => {
  ws.websocketSubscribe(topic, (msg) => {
    const { code, message } = JSON.parse(msg.body)
    if (code === 200) {
      // todo
    } else {
      Notification.error({
        title: '提示',
        closable: true,
        content: `训练出错：${message}`,
        duration: 0
      })
    }
    resetPartTrainForm()
    loadList()
  })
}

onMounted(() => {
  sub()
  if (props.satelliteInfo?.satelliteId) {
    trainForm.value.satelliteId = props.satelliteInfo.satelliteId
    trainForm.value.satelliteCode = props.satelliteInfo.code
    trainForm.value.satelliteName = props.satelliteInfo.name
    handleSearch('')
    loadList()
    algorithmSpinner()
  }
})
onBeforeUnmount(() => {
  ws.websocketUnsubscribe(topic)
})
</script>

<template>
  <div class="tabs-train">
    <div class="tabs-content-left" ref="manualChartsDomRef">
      <div class="chart-card">
        <chart-line :dataSource="dataSource" :loading="chartLoading" />
      </div>
      <div class="table-card">
        <div class="title">模型列表</div>
        <div class="table-wrapper">
          <a-table
            :columns="columns"
            :data="modelPageData"
            :pagination="false"
            :scroll="scroll"
            :scrollbar="false"
            :loading="tableLoading"
          >
            <template #action="{ record }">
              <a-button
                status="success"
                size="mini"
                type="text"
                @click="handleDetail(record)"
                :disabled="record.status !== 2"
              >
                <template #icon>
                  <icon-file />
                </template>
                详情
              </a-button>
              <a-button status="danger" size="mini" type="text" @click="handleDelete(record)">
                <template #icon>
                  <IconDelete />
                </template>
                删除
              </a-button>
            </template>
          </a-table>
        </div>
        <div class="table-card-footer">
          <a-pagination
            :total="pagination.total"
            :current="pagination.page"
            :page-size="pagination.pageSize"
            show-total
            show-jumper
            show-page-size
            @change="handlePageChange"
            @page-size-change="handlePageSizeChange"
          />
        </div>
      </div>
    </div>
    <div class="tabs-content-right">
      <div style="text-align: center">
        <div style="font-size: 18px; font-weight: bold">模型训练参数</div>
        <a-divider style="margin: 4px 0" dashed />
      </div>
      <a-spin :loading="formLoading">
        <a-form :model="trainForm" @submit="startTrain" :rules="trainFormRules" layout="vertical" ref="trainFormRef">
          <a-form-item field="isOldModel" label="是否使用旧模型" class="form-item-switch">
            <a-switch v-model="trainForm.isOldModel" @change="switchChange">
              <template #checked>否</template>
              <template #unchecked>是</template>
            </a-switch>
          </a-form-item>
          <a-form-item field="modelName" label="模型名称" v-if="!trainForm.isOldModel">
            <a-input v-model="trainForm.modelName" placeholder="请填写模型名称" />
          </a-form-item>
          <a-form-item field="oldModelName" label="旧模型名称" v-else>
            <a-select
              :loading="modelSelectLoading"
              v-model="trainForm.oldModelName"
              placeholder="请选择旧模型"
              allow-clear
            >
              <a-option v-for="item of oldModelOptions" :key="item.id" :value="item.id">{{ item.modelName }} </a-option>
            </a-select>
          </a-form-item>
          <a-form-item field="paramCode" label="选择参数">
            <a-select
              :loading="paramSelectLoading"
              :placeholder="computedSearchSelectPlaceholder"
              @search="handleSearch"
              :filter-option="false"
              v-model="trainForm.paramCode"
              @change="paramCodeChange"
              allow-search
              allow-clear
            >
              <a-option v-for="item of paramOptions" :key="item.telemetryCode" :value="item.telemetryCode"
                >{{ item.paramName }}
              </a-option>
            </a-select>
          </a-form-item>
          <a-form-item field="startTime" label="开始时间">
            <a-date-picker
              v-model="trainForm.startTime"
              placeholder="请选择开始时间"
              show-time
              style="width: 100%"
              format="YYYY-MM-DD HH:mm:ss"
            />
          </a-form-item>
          <a-form-item field="endTime" label="结束时间">
            <a-date-picker
              v-model="trainForm.endTime"
              placeholder="请选择结束时间"
              show-time
              style="width: 100%"
              format="YYYY-MM-DD HH:mm:ss"
            />
          </a-form-item>
          <a-form-item field="algorithmName" label="选择算法">
            <a-select
              :loading="algorithmSelectLoading"
              v-model="trainForm.algorithmName"
              placeholder="请选择算法"
              allow-clear
              @change="algorithmSelectChange"
            >
              <a-option :key="item.id" v-for="item of algorithmOptions" :value="item.algorithmName"
                >{{ item.algorithmName }}
              </a-option>
            </a-select>
          </a-form-item>
          <a-form-item class="form-bottom">
            <a-link @click="editAlgorithmParams">修改模型参数</a-link>
            <div class="btn">
              <a-button type="primary" html-type="submit">开始训练</a-button>
            </div>
          </a-form-item>
        </a-form>
      </a-spin>
    </div>
  </div>
  <a-modal title="修改参数" v-model:visible="editAlgorithmParasModelVisible" :on-before-cancel="handleBeforeCancel">
    <a-form :model="trainForm.algorithmParamList" auto-label-width>
      <template v-for="(item, index) in trainForm.algorithmParamList" :key="index">
        <a-form-item
          v-if="item.paramValueStart"
          field="name"
          :tooltip="`建议值${item.paramValueStart}`"
          :label="item.paramNameCn"
        >
          <a-input v-model="item.paramValueEnd" :placeholder="`请输入${item.paramNameCn}`" allow-clear />
        </a-form-item>
        <a-form-item v-else field="name" :label="item.paramNameCn">
          <a-select placeholder="请选择" v-model="item.paramValueEnd" allow-clear>
            <a-option v-for="(subItem, index) of item.selectInfo.split(',')" :key="index" :value="subItem"
              >{{ subItem }}
            </a-option>
          </a-select>
        </a-form-item>
      </template>
    </a-form>
    <template #footer>
      <a-button type="outline" @click="handleEditReset">重置</a-button>
      <a-button type="primary" @click="editAlgorithmParasModelVisible = false">确认 </a-button>
    </template>
  </a-modal>
</template>

<style scoped lang="less">
@import '@/views/trendForecast/style/train';
</style>
